package dpss.replicamanager.data;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import dpss.commons.ServerNode;
import dpss.replicamanager.gui.Constants;

/**
 * The map of all the available clusters
 * 
 * @author musab.mirza
 * 
 */
public class ServerNodes extends Vector<ServerNode> {

	private static final long serialVersionUID = 1L;

	/**
	 * Overriding the toString method
	 * @return 
	 */
	public String toString() {
		
		String toPrint = "---- CURRENT SERVER NODES ---\n";
		for (ServerNode n : this) {
			toPrint = toPrint + n.getSocketAddress().toString() + " => " + n.toString() + "\n\n";
		}
		return toPrint;
	}

	/**
	 * Registers a new server node if it already doesn't exist
	 * 
	 * @param ip
	 * @param port
	 */
	public synchronized boolean register(InetSocketAddress socketAddress) {
		if (get(socketAddress) != null) {
			return false;
		} else {
			ServerNode serverNode = null;
			serverNode = new ServerNode(socketAddress, "", false, 0);
			add(serverNode);
			return true;
		}

	}

	public ServerNode get(InetSocketAddress socketAddress) {
		for (ServerNode serverNode : this) {
			if (serverNode.getSocketAddress().equals(socketAddress)) {
				return serverNode;
			}
		}
		return null;
	}

	/**
	 * Loops through and removes (deregisters) a node
	 * 
	 * @param ip
	 * @param port
	 */
	public synchronized void deregister(InetSocketAddress socketAddress) {
		Iterator<ServerNode> iter = this.iterator();
		while (iter.hasNext()) {
			ServerNode n = iter.next();
			if (n.getSocketAddress().equals(socketAddress))
				iter.remove();
		}
	}

	/**
	 * Returns the data vector to be used in the GUI
	 * 
	 * @return
	 */
	public Vector getDataVector() {

		Vector<Vector> data = new Vector<Vector>();

		for (ServerNode n : this) {
			Vector row = new Vector();
			row.add(n.getSocketAddress().getHostName() + ":"
					+ n.getSocketAddress().getPort());

			if (n.getGroup().isEmpty())
				row.add("uninitialized");
			else
				row.add(n.getGroup());
			row.add(n.isLeader());
			row.add(n.getFailCount());
			row.add(n.isFaulty());
			data.add(row);
		}

		return data;
	}

	/**
	 * Converts from a vector row data to a ServerNode object
	 * 
	 * @param rowData
	 * @return
	 */
	public static ServerNode vector2ServerNode(Vector rowData) {
		HashMap<String, Object> toReturn = new HashMap<String, Object>();
		if (rowData != null) {

			InetSocketAddress address = string2InetSocketAddress(((String) rowData
					.get(Constants.LISTING_COLUMN_ADDRESS)));
			String group = (String) rowData.get(Constants.LISTING_COLUMN_GROUP);
			boolean isLeader = (boolean) rowData
					.get(Constants.LISTING_COLUMN_LEADER);
			Integer failCounts = Integer.parseInt((String) rowData
					.get(Constants.LISTING_COLUMN_FAILS));

			return new ServerNode(address, group, isLeader, failCounts);
		}
		return null;

	}

	/**
	 * Given a String returns the InetSocketAddress
	 * 
	 * @param str
	 * @return
	 */
	public static InetSocketAddress string2InetSocketAddress(String str) {
		String[] parts = str.split(":");

		try {
			InetAddress host = InetAddress.getByName(parts[0]);
			Integer port = Integer.parseInt(parts[1]);
			return new InetSocketAddress(host, port);
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Returns all the available Leader nodes
	 * 
	 * @return
	 */
	public synchronized HashSet<ServerNode> getAllLeaders() {
		HashSet<ServerNode> toReturn = new HashSet<ServerNode>();
		for (ServerNode n : this) {
			if (n.isLeader())
				toReturn.add(n);
		}
		return toReturn;
	}

	/**
	 * Returns other leaders as a HashMap
	 * 
	 * @param group
	 * @return
	 */
	public synchronized HashMap<String, InetSocketAddress> getOtherLeaders(
			String group) {
		HashMap<String, InetSocketAddress> otherLeaders = new HashMap<String, InetSocketAddress>();
		for (ServerNode n : getAllLeaders()) {
			if (!n.getGroup().equalsIgnoreCase(group)) {
				otherLeaders.put(n.getGroup(), n.getSocketAddress());
			}
		}
		return otherLeaders;
	}

	/**
	 * Checks if a leader exists for a given gorup
	 * 
	 * @param group
	 * @return
	 */
	public synchronized boolean leaderExists(String group) {

		for (ServerNode n : this) {
			if (n.getGroup().equalsIgnoreCase(group) && n.isLeader())
				return true;
		}

		return false;
	}

	/**
	 * 
	 * Gets a particular server node by key (inetsocketaddress)
	 * 
	 * @param key
	 * @return
	 */
	public ServerNode find(InetSocketAddress key) {
		for (ServerNode n : this) {
			if (keysMatch(n.getSocketAddress(), key))
				return n;
		}
		return null;
	}

	/**
	 * Replaces a servernode with another one, given the key
	 * 
	 * @param socketAddress
	 * @param serverNode
	 */
	public synchronized void replace(InetSocketAddress key,
			ServerNode newServerNode) {
		for (ServerNode n : this) {
			if (keysMatch(n.getSocketAddress(), key)){
				n = newServerNode;
				return;
			}
		}
	}

	/**
	 * Removes by key
	 * @param senderSocketAddress
	 */
	public void removeByKey(InetSocketAddress key) {
		Iterator<ServerNode> iter = this.iterator();
		while (iter.hasNext()) {
			ServerNode n = iter.next();
			if (keysMatch(n.getSocketAddress(), key))
				iter.remove();
		}
	}
	
	/**
	 * Checks if two keys match
	 * @param a
	 * @param b
	 * @return
	 */
	public boolean keysMatch(InetSocketAddress a, InetSocketAddress b){
		if (a.getHostName().equalsIgnoreCase(b.getHostName()) && a.getPort() == b.getPort())
			return true;
		return false;
	}

	/**
	 * Gets leader address by group name
	 * 
	 * @param group
	 * @return
	 */
	public InetSocketAddress getLeaderAddress(String group) {
		for (ServerNode n : this){
			if (n.getGroup().equalsIgnoreCase(group) && n.isLeader()){
				return n.getSocketAddress();
			}
		}
		return null;
	}

	/**
	 * Forms a set of all replicas of a given group
	 * @param group
	 * @return
	 */
	public HashSet<InetSocketAddress> getAllGroupReplicas(String group) {
		HashSet<InetSocketAddress> toReturn = new HashSet<InetSocketAddress>();
		for (ServerNode n : this){
			if (n.getGroup().equalsIgnoreCase(group) && !n.isLeader()){
				toReturn.add(n.getSocketAddress());
			}
		}
		return toReturn;
	}
	
	

}
